import * as React from "react";

type Updater<T> = T | ((prevValue: T) => T);

export type SetState<T> = (nextValue: Updater<T>, ignoreDestroy?: boolean) => void;

export function useSafeState<T>(defaultValue?: T | (() => T)): [T, SetState<T>] {
  const destroyRef = React.useRef(false);
  const [value, setValue] = React.useState<T>(defaultValue!);

  React.useEffect(() => {
    destroyRef.current = false;

    return () => {
      destroyRef.current = true;
    };
  }, []);

  function safeSetState(updater: Updater<T>, ignoreDestroy?: boolean) {
    if (ignoreDestroy && destroyRef.current) return;

    setValue(updater);
  }

  return [value, safeSetState];
}

export function useMergedState<T, R = T>(
  defaultStateValue: T | (() => T),
  option?: {
    defaultValue?: T | (() => T);
    value?: T;
    onChange?: (value: T, prevValue: T) => void;
    postState?: (value: T) => T;
  },
): [R, (value: T, ignoreDestroy?: boolean) => void] {
  const { defaultValue, value, onChange, postState } = option || {};
  const [innerValue, setInnerValue] = useSafeState<T>(() => {
    if (value !== undefined) return value;

    if (defaultValue !== undefined) {
      return typeof defaultValue === "function" ? (defaultValue as any)() : defaultValue;
    }
    return typeof defaultStateValue === "function"
      ? (defaultStateValue as any)()
      : defaultStateValue;
  });

  let mergedValue = value !== undefined ? value : innerValue;
  if (postState) mergedValue = postState(mergedValue);

  const onChangeRef = React.useRef(onChange);
  onChangeRef.current = onChange;

  const triggerChange = React.useCallback(
    (newValue: T, ignoreDestroy?: boolean) => {
      setInnerValue(newValue, ignoreDestroy);
      if (mergedValue !== newValue && onChangeRef.current)
        onChangeRef.current(newValue, mergedValue);
    },
    [mergedValue, onChangeRef],
  );

  const prevValueRef = React.useRef(value);

  React.useEffect(() => {
    if (value === undefined && value !== prevValueRef.current) setInnerValue(value!);
    prevValueRef.current = value;
  }, [value]);

  return [mergedValue as unknown as R, triggerChange];
}

export default useMergedState;
